package com.whjh.image.conpression.task;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.coobird.thumbnailator.Thumbnails;

/**
 * 图片压缩工具
 * 创建人 LITAO
 * 创建时间 2021年4月16日
 */
@SuppressWarnings("deprecation")
public class ThumbnailsUtils {
    private static Logger logger = LoggerFactory.getLogger(ThumbnailsUtils.class);

    /**
     * 按文件大小压缩一个输入流返回一个输出流
     * @param imageBytes 图片字节数组
     * @param confineSize 限制文件大小 kb
     * @param imageName 图片名称
     * @return
     */
    public static ByteArrayOutputStream compressToOut(byte[] imageBytes, int confineSize, String imageName){
        try {
            byte[] bytes = compressPicForScaleToBytes(imageBytes, confineSize, imageName);
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            //把输入流拷贝到输出流
            IOUtils.copy(in,out);
            in.close();
            return out;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {

        }
        return  null;
    }
    /**
     * 按文件大小压缩一个图片字节数组返回一个输入流
     * @param imageBytes 图片字节数组
     * @param confineSize 限制文件大小 kb
     * @param imageName 图片名称
     */
    public static ByteArrayInputStream compressIn(byte[] imageBytes, int confineSize, String imageName){

        try {
            byte[] bytes = compressPicForScaleToBytes(imageBytes, confineSize, imageName);
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            return in;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  null;
    }
    /**
     * 按文件大小压缩一个图片字节数组到本地文件
     * @param imageBytes 图片字节数组
     * @param toFile 目标文件名称路径
     * @param confineSize 限制文件大小 kb
     * @param imageName 图片名称
     * @return
     */
	public static void compressFile(byte[] imageBytes, String toFile, int confineSize, String imageName){
        try {
            byte[] bytes = compressPicForScaleToBytes(imageBytes, confineSize, imageName);
            OutputStream out = new FileOutputStream(toFile);
            out.write(bytes);
            out.flush();
            IOUtils.closeQuietly(out);
            out = null;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    /**
     * 根据指定大小压缩图片
     *
     * @param imageBytes  源图片字节数组
     * @param desFileSize 指定图片大小，单位kb
     * @param imageName     影像名称
     * @return 压缩质量后的图片字节数组
     */
    public static byte[] compressPicForScaleToBytes(byte[] imageBytes, long desFileSize, String imageName) {
        if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
            return imageBytes;
        }
        logger.info("【开始图片压缩 ..】imageName={}", imageName);
        byte[] imageBytesNew = new byte[imageBytes.length];
        System.arraycopy(imageBytes, 0, imageBytesNew, 0, imageBytes.length);
        double quality = 0.99;
        try {
            while (imageBytesNew.length > desFileSize * 1024) {
                ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
                Thumbnails.of(inputStream)
                        .scale(1)
                        .outputQuality(quality)
                        .toOutputStream(outputStream);
                imageBytesNew = outputStream.toByteArray();
                //关闭流
                IOUtils.closeQuietly(inputStream,outputStream);
                inputStream = null;
                outputStream = null;
                quality = quality - 0.01;
            }
            logger.info("【图片压缩 over!!!】压缩比={} imageName={}", quality, imageName);
        } catch (Exception e) {
            logger.error("【图片压缩失败!】msg=" + imageName, e);
        }
        return imageBytesNew;
    }
    
    /**
     * 二分法压缩
     * @param imageBytes
     * @param desFileSize
     * @param imageName
     * @return
     */
    public static byte[] compressPicForScaleToBytesBinarySearch(byte[] imageBytes, long desFileSize, String imageName) {
    	    if (imageBytes == null || imageBytes.length <= 0 || imageBytes.length < desFileSize * 1024) {
    	        return imageBytes;
    	    }
    	    logger.info("【开始图片压缩 ..】imageName={}", imageName);
    	    long start = System.currentTimeMillis();
    	    double minQuality = 0.01; // 最小质量
    	    double maxQuality = 1.0; // 最大质量
    	    double currentQuality = (minQuality + maxQuality) / 2; // 初始质量为中点
    	    byte[] imageBytesNew = imageBytes;
    	    try {
    	        while (maxQuality - minQuality > 0.001) { // 当质量差小于0.001时停止
    	            imageBytesNew = compressWithQuality(imageBytes, currentQuality);
    	            
    	            if (imageBytesNew.length < desFileSize * 1024) {
    	                minQuality = currentQuality; // 如果小于目标大小，提高质量下限
    	            } else {
    	                maxQuality = currentQuality; // 如果大于或等于目标大小，降低质量上限
    	            }

    	            currentQuality = (minQuality + maxQuality) / 2; // 更新当前质量
    	        }

    	        // 在循环结束后，currentQuality 可能使得图片大小等于或略小于目标大小。
    	        // 我们再次检查并确保结果严格小于目标大小。
    	        byte[] finalImageBytes = compressWithQuality(imageBytes, currentQuality);
    	        while (finalImageBytes.length >= desFileSize * 1024) {
    	            currentQuality -= 0.001; // 小幅降低质量
    	            finalImageBytes = compressWithQuality(imageBytes, currentQuality);
    	        }
    	        long time = System.currentTimeMillis() - start;
    	        logger.info("【图片压缩 over!!!】耗时={}毫秒 压缩比={} imageName={}", time, currentQuality, imageName);
    	    } catch (Exception e) {
    	        logger.error("【图片压缩失败!】msg=" + imageName, e);
    	    }

    	    return imageBytesNew;
    	}

    	private static byte[] compressWithQuality(byte[] imageBytes, double quality) throws IOException {
    	    ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
    	    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    	    
    	    Thumbnails.of(inputStream)
    	              .scale(1)
    	              .outputQuality(quality)
    	              .toOutputStream(outputStream);
    	              
    	    byte[] compressedBytes = outputStream.toByteArray();
    	    IOUtils.closeQuietly(inputStream, outputStream);
    	    return compressedBytes;
    	}
}

